package ace.cmp.redisson.config;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.api.RedissonReactiveClient;
import org.redisson.api.RedissonRxClient;
import org.redisson.config.ClusterServersConfig;
import org.redisson.config.Config;
import org.redisson.config.SentinelServersConfig;
import org.redisson.config.SingleServerConfig;
import org.redisson.spring.data.connection.RedissonConnectionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties.Sentinel;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.io.Resource;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.util.ReflectionUtils;

/**
 * @author caspar
 * @date 2023/3/9 11:49
 */
@ConditionalOnProperty(
    prefix = AceRedisProperties.PREFIX,
    value = "enable",
    havingValue = "true",
    matchIfMissing = true)
@Configuration
@ConditionalOnClass({Redisson.class, RedisOperations.class})
@AutoConfigureBefore({RedisAutoConfiguration.class})
@EnableConfigurationProperties({
  RedissonProperties.class,
  RedisProperties.class,
  AceRedisProperties.class
})
public class RedissonAutoConfiguration {
  private static final String REDIS_PROTOCOL_PREFIX = "redis://";
  private static final String REDISS_PROTOCOL_PREFIX = "rediss://";

  @Autowired(required = false)
  private List<RedissonAutoConfigurationCustomizer> redissonAutoConfigurationCustomizers;

  @Autowired private RedissonProperties redissonProperties;
  @Autowired private RedisProperties redisProperties;
  @Autowired private ApplicationContext ctx;

  public RedissonAutoConfiguration() {}

  @Bean
  @ConditionalOnMissingBean(name = {"redisTemplate"})
  public RedisTemplate<Object, Object> redisTemplate(
      RedisConnectionFactory redisConnectionFactory) {
    RedisTemplate<Object, Object> template = new RedisTemplate();
    template.setConnectionFactory(redisConnectionFactory);
    return template;
  }

  @Bean
  @ConditionalOnMissingBean({StringRedisTemplate.class})
  public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
    StringRedisTemplate template = new StringRedisTemplate();
    template.setConnectionFactory(redisConnectionFactory);
    return template;
  }

  @Bean
  @ConditionalOnMissingBean({RedisConnectionFactory.class})
  public RedissonConnectionFactory redissonConnectionFactory(RedissonClient redisson) {
    return new RedissonConnectionFactory(redisson);
  }

  @Bean
  @Lazy
  @ConditionalOnMissingBean({RedissonReactiveClient.class})
  public RedissonReactiveClient redissonReactive(RedissonClient redisson) {
    return redisson.reactive();
  }

  @Bean
  @Lazy
  @ConditionalOnMissingBean({RedissonRxClient.class})
  public RedissonRxClient redissonRxJava(RedissonClient redisson) {
    return redisson.rxJava();
  }

  @Bean(destroyMethod = "shutdown")
  @ConditionalOnMissingBean({RedissonClient.class})
  public RedissonClient redisson() throws IOException {
    Method clusterMethod = ReflectionUtils.findMethod(RedisProperties.class, "getCluster");
    Method usernameMethod = ReflectionUtils.findMethod(RedisProperties.class, "getUsername");
    Method timeoutMethod = ReflectionUtils.findMethod(RedisProperties.class, "getTimeout");
    Method connectTimeoutMethod =
        ReflectionUtils.findMethod(RedisProperties.class, "getConnectTimeout");
    Method clientNameMethod = ReflectionUtils.findMethod(RedisProperties.class, "getClientName");
    Object timeoutValue = ReflectionUtils.invokeMethod(timeoutMethod, this.redisProperties);
    Integer timeout = null;
    if (timeoutValue instanceof Duration) {
      timeout = (int) ((Duration) timeoutValue).toMillis();
    } else if (timeoutValue != null) {
      timeout = (Integer) timeoutValue;
    }

    Integer connectTimeout = null;
    if (connectTimeoutMethod != null) {
      Object connectTimeoutValue =
          ReflectionUtils.invokeMethod(connectTimeoutMethod, this.redisProperties);
      if (connectTimeoutValue != null) {
        connectTimeout = (int) ((Duration) connectTimeoutValue).toMillis();
      }
    } else {
      connectTimeout = timeout;
    }

    String clientName = null;
    if (clientNameMethod != null) {
      clientName = (String) ReflectionUtils.invokeMethod(clientNameMethod, this.redisProperties);
    }

    String username = null;
    if (usernameMethod != null) {
      username = (String) ReflectionUtils.invokeMethod(usernameMethod, this.redisProperties);
    }

    Config config;
    if (this.redissonProperties.getConfig() != null) {
      try {
        config = Config.fromYAML(this.redissonProperties.getConfig());
      } catch (IOException var20) {
        try {
          config = Config.fromJSON(this.redissonProperties.getConfig());
        } catch (IOException var19) {
          var19.addSuppressed(var20);
          throw new IllegalArgumentException("Can't parse config", var19);
        }
      }
    } else if (this.redissonProperties.getFile() != null) {
      try {
        InputStream is = this.getConfigStream();
        config = Config.fromYAML(is);
      } catch (IOException var18) {
        try {
          InputStream is = this.getConfigStream();
          config = Config.fromJSON(is);
        } catch (IOException var17) {
          var17.addSuppressed(var18);
          throw new IllegalArgumentException("Can't parse config", var17);
        }
      }
    } else if (this.redisProperties.getSentinel() != null) {
      Method nodesMethod = ReflectionUtils.findMethod(Sentinel.class, "getNodes");
      Object nodesValue =
          ReflectionUtils.invokeMethod(nodesMethod, this.redisProperties.getSentinel());
      String[] nodes;
      if (nodesValue instanceof String) {
        nodes = this.convert(Arrays.asList(((String) nodesValue).split(",")));
      } else {
        nodes = this.convert((List) nodesValue);
      }

      config = new Config();
      SentinelServersConfig c =
          (SentinelServersConfig)
              ((SentinelServersConfig)
                      ((SentinelServersConfig)
                              config
                                  .useSentinelServers()
                                  .setMasterName(this.redisProperties.getSentinel().getMaster())
                                  .addSentinelAddress(nodes)
                                  .setDatabase(this.redisProperties.getDatabase())
                                  .setUsername(username))
                          .setPassword(this.redisProperties.getPassword()))
                  .setClientName(clientName);
      if (connectTimeout != null) {
        c.setConnectTimeout(connectTimeout);
      }

      if (connectTimeoutMethod != null && timeout != null) {
        c.setTimeout(timeout);
      }
    } else {
      Method method;
      if (clusterMethod != null
          && ReflectionUtils.invokeMethod(clusterMethod, this.redisProperties) != null) {
        Object clusterObject = ReflectionUtils.invokeMethod(clusterMethod, this.redisProperties);
        method = ReflectionUtils.findMethod(clusterObject.getClass(), "getNodes");
        List<String> nodesObject = (List) ReflectionUtils.invokeMethod(method, clusterObject);
        String[] nodes = this.convert(nodesObject);
        config = new Config();
        ClusterServersConfig c =
            (ClusterServersConfig)
                ((ClusterServersConfig)
                        ((ClusterServersConfig)
                                config
                                    .useClusterServers()
                                    .addNodeAddress(nodes)
                                    .setUsername(username))
                            .setPassword(this.redisProperties.getPassword()))
                    .setClientName(clientName);
        if (connectTimeout != null) {
          c.setConnectTimeout(connectTimeout);
        }

        if (connectTimeoutMethod != null && timeout != null) {
          c.setTimeout(timeout);
        }
      } else {
        config = new Config();
        String prefix = "redis://";
        method = ReflectionUtils.findMethod(RedisProperties.class, "isSsl");
        if (method != null
            && (Boolean) ReflectionUtils.invokeMethod(method, this.redisProperties)) {
          prefix = "rediss://";
        }

        SingleServerConfig c =
            (SingleServerConfig)
                ((SingleServerConfig)
                        ((SingleServerConfig)
                                config
                                    .useSingleServer()
                                    .setAddress(
                                        prefix
                                            + this.redisProperties.getHost()
                                            + ":"
                                            + this.redisProperties.getPort())
                                    .setDatabase(this.redisProperties.getDatabase())
                                    .setUsername(username))
                            .setPassword(this.redisProperties.getPassword()))
                    .setClientName(clientName);
        if (connectTimeout != null) {
          c.setConnectTimeout(connectTimeout);
        }

        if (connectTimeoutMethod != null && timeout != null) {
          c.setTimeout(timeout);
        }
      }
    }

    if (this.redissonAutoConfigurationCustomizers != null) {
      Iterator var27 = this.redissonAutoConfigurationCustomizers.iterator();

      while (var27.hasNext()) {
        RedissonAutoConfigurationCustomizer customizer =
            (RedissonAutoConfigurationCustomizer) var27.next();
        customizer.customize(config);
      }
    }

    return Redisson.create(config);
  }

  private String[] convert(List<String> nodesObject) {
    List<String> nodes = new ArrayList(nodesObject.size());
    Iterator var3 = nodesObject.iterator();

    while (true) {
      while (var3.hasNext()) {
        String node = (String) var3.next();
        if (!node.startsWith("redis://") && !node.startsWith("rediss://")) {
          nodes.add("redis://" + node);
        } else {
          nodes.add(node);
        }
      }

      return (String[]) nodes.toArray(new String[0]);
    }
  }

  private InputStream getConfigStream() throws IOException {
    Resource resource = this.ctx.getResource(this.redissonProperties.getFile());
    return resource.getInputStream();
  }
}
